﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using Psm.Container;
using Psm.Logging;
using Psm.Transport;
using System.Net;
using System.ComponentModel.Composition;
using System.Threading;
using Psm.Util;

namespace Psm.Transport.TcpTransport
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export("TCP_TRANSPORT_CLIENT", typeof(IRemoteTransport))]
    class TcpClient : IRemoteTransport
    {
        private static ILogger logger = Repository.GetInstance<ILogger>();

        private bool sending = false;
        private Queue<ByteBuffer> sendQueue = new Queue<ByteBuffer>();
        private SpinLock queueLock = new SpinLock();
        

        private System.Net.Sockets.Socket clientSocket;
        
        
        private ByteBuffer sendBuffer;
        private int sentLength = 0;
        private ByteBuffer receiveBuffer;
        private Exception socketException;
        private Action<IClientTransport> receiveCallback;
        private ManualResetEvent closeEvent = new ManualResetEvent(false);
        public event EventHandler<TransportClosedEventArgs> TransportClosed;


        private TcpHostConfiguration configuration;

        public TcpClient(System.Net.Sockets.Socket clientSocket)
        {   
            this.clientSocket = clientSocket;
            this.clientSocket.SendBufferSize = 0;
            //this.clientSocket.ReceiveBufferSize = 8192 * 3;
        }

        public TcpClient()
        {
        }

        public void SendAsync(ByteBuffer payload)
        {
            if (payload == null) throw new ArgumentNullException("payload");
            TriggerSend(payload);
        }

        protected void TriggerSend(ByteBuffer payload)
        {
            bool callSend = false;
            bool taken = false;
            queueLock.Enter(ref taken);
            //lock(this.sendQueue)
            {
                if (!sending)
                {
                    callSend = true;
                    sending = true;
                }
                else
                {
                    this.sendQueue.Enqueue(payload);
                    callSend = false;
                }
            }
            queueLock.Exit(true);
            if (callSend)
                SendMessage(payload);
        }

        private void SendMessage(ByteBuffer msgBuffer)
        {
            try
            {
                if (msgBuffer != null)
                {
                    sendBuffer = msgBuffer;
                    sentLength = sendBuffer.StartIndex;
                    clientSocket.BeginSend(sendBuffer.GetBuffer(), sentLength, sendBuffer.Length - sentLength, 0, new AsyncCallback(this.SendCallback), null);
                }
                else
                {
                    this.clientSocket.Shutdown(System.Net.Sockets.SocketShutdown.Send);
                }
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
                OnTransportClosed();
            }
        }

        public void SendCallback(IAsyncResult ar)
        {
            try
            {
                sentLength += clientSocket.EndSend(ar);
                if (sentLength == sendBuffer.Length)
                {
                    bool callSend = false;
                    bool taken = false;
                    ByteBuffer buffer = null;
                    //lock (this.sendQueue)
                    {
                        queueLock.Enter(ref taken);
                        if (this.sendQueue.Count > 0)
                        {
                            callSend = true;
                            buffer = this.sendQueue.Dequeue();
                        }
                        else
                        {
                            callSend = sending = false;
                        }
                    }
                    queueLock.Exit(true);
                    if( callSend )
                        SendMessage(buffer);
                }
                else
                {   
                    clientSocket.BeginSend(sendBuffer.GetBuffer(), sentLength, sendBuffer.Length - sentLength, 0, new AsyncCallback(this.SendCallback), null);
                }
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public void BeginReceive(ByteBuffer buffer, Action<IClientTransport> asyncCallback)
        {
            receiveCallback = asyncCallback;
            receiveBuffer = buffer;
            clientSocket.BeginReceive(buffer.GetBuffer(), buffer.CurrentIndex, buffer.FreeCapacity, 0, new AsyncCallback(this.ReceiveCallback), null);
        }

        void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                int receivedBytes = this.clientSocket.EndReceive(ar);
                if (receivedBytes == 0)
                {
                    CloseConnection();
                    OnTransportClosed();
                    return;
                }
                else
                {
                    receiveBuffer.Pad(receivedBytes);
                }
            }
            catch (Exception ex)
            {
                socketException = ex;
                CloseConnection();
                OnTransportClosed();
            }
            receiveCallback(this);
        }

        private void CloseConnection()
        {
            this.clientSocket.Close();
            closeEvent.Set();
        }

        private void OnTransportClosed()
        {
            EventHandler<TransportClosedEventArgs> handler = this.TransportClosed;
            if (handler != null)
                handler(this, new TransportClosedEventArgs());
        }

        public ByteBuffer EndReceive()
        {
            if (socketException != null) throw socketException;
            return receiveBuffer;
        }

        public void Open()
        {   
            IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(configuration.Ip), int.Parse(configuration.Port));
            this.clientSocket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork,System.Net.Sockets.SocketType.Stream,System.Net.Sockets.ProtocolType.Tcp);
            this.clientSocket.SendBufferSize = 0;
            //this.clientSocket.ReceiveBufferSize = 8192 * 3;
            this.clientSocket.Connect(remoteEP);
        }

        public void Close()
        {
            TriggerSend(null);
            closeEvent.WaitOne();
        }

        public void Configure(string configuration)
        {
            this.configuration = TcpHostsConfiguration.GetConfiguration(configuration);
        }
    }
}
